package io.wongxd.demo.mvi.core

import androidx.lifecycle.viewModelScope
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch

abstract class BaseMVIVM<UiIntent : IUiIntent, UiState : IUiState, UIEffect : IUIEffect> : BaseVM() {

    // 可变状态数据流（读写）
    private val _uiStateFlow by lazy { MutableStateFlow(value = initUiState()) }

    // 可观察状态数据流（只读）
    val uiStateFlow: StateFlow<UiState> by lazy { _uiStateFlow }

    /** 更新页面状态 **/
    fun updateUiState(reducer: UiState.() -> UiState) {
        _uiStateFlow.update { reducer(_uiStateFlow.value) }
    }

    /** 发送页面事件 **/
    fun sendUiIntent(uiIntent: UiIntent) {
        viewModelScope.launch {
            _uiIntentChannel.send(uiIntent)
        }
    }

    // Channel 类似于阻塞队列，但它是挂起的，适用于协程之间的通信。它可以配置为不同的模式，如缓存大小和发送行为。适用于事件的生产者-消费者模型，如任务执行、消息传递等
    // 创建意图管道，容量无限大 （可以用Flow的监听效果，观察者模式改变之后就自动更新）
    // 为什么用Channel不用Flow，是因为只需要单向流动就行了，Channel是单对单，Flow的单对多
    // Channel的发送send和接收receive,发出的事件只能被接收一次，接收之后就不能再次接收了，很适合这个场景。 万一屏幕旋转重建了也不会再度触发事件。
    // 页面事件的 Channel 分发
    private val _uiIntentChannel = Channel<UiIntent>(Channel.UNLIMITED)

    init {
        // 这里是通过Channel的方式自动分发的。
        viewModelScope.launch {
            // 收集意图 （观察者模式改变之后就自动更新）用于协程通信的，所以需要在协程中调用
            _uiIntentChannel.consumeAsFlow().collect { intent ->
                handleIntent(intent)
            }
        }
    }

    // 一次性事件，无需更新
    private val _effectFlow = MutableSharedFlow<UIEffect>()
    val uiEffectFlow: SharedFlow<UIEffect> by lazy { _effectFlow.asSharedFlow() }

    /** 两种方式发射 - 1 **/
    protected fun sendEffect(builder: suspend () -> UIEffect?) = viewModelScope.launch {
        builder()?.let { _effectFlow.emit(it) }
    }

    /** 两种方式发射 - 2 **/
    protected suspend fun sendEffect(effect: UIEffect) = _effectFlow.emit(effect)

    /** 每个页面的 UiState 都不相同，必须实自己去创建 **/
    protected abstract fun initUiState(): UiState

    /** 每个页面处理的 UiIntent 都不同，必须实现自己页面对应的状态处理 **/
    protected abstract fun handleIntent(intent: UiIntent)

}